/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */

package org.ponly.cta4j.service;

import java.util.*;
import org.ponly.cta4j.Captcha;
import org.ponly.cta4j.CaptchaFactory;
import org.ponly.cta4j.service.store.CaptchaStore;

/**
 * 抽象的验证码实现基类, 该类实现了对超过最小有效期的验证码的垃圾回收.
 * <p/>
 * 垃圾回收说明: <br/>
 * 默认情况下, 当达到运行垃圾回收的阈值时运行垃圾回收, 某些情况下可能希望通过定时任务来进行垃圾收集, 此时可以设置垃圾回收阈值为0,
 * 通过外部定时任务定时调用 {@link #garbageCollectCaptchaStore()} 来进行垃圾收集工作.
 *
 * @author jcaptcha
 * @author vacoor
 * @since 1.0
 */
public class AbstractManageableCaptchaService extends AbstractCaptchaService {

    /**
     * 最小有效期, 验证码服务会保证在该时间内, 验证码不会被回收, 超过这个时间后将可能被回收(当垃圾回收时).
     */
    private int minGuarantedStorageDelayInSeconds;

    /**
     * 最多存储验证码的条数.
     */
    private int captchaStoreMaxSize;

    /**
     * 运行垃圾收集器的阈值.
     */
    private int captchaStoreSizeBeforeGarbageCollection;

    // 最老的验证码生效时间.
    private transient long oldestCaptcha = Long.MAX_VALUE;   // OPTIMIZATION STUFF!

    /**
     * @param captchaFactory
     * @param captchaStore
     * @param minGuarantedStorageDelayInSeconds       验证码最小有效期
     * @param captchaStoreMaxSize                     验证码最大容量
     * @param captchaStoreSizeBeforeGarbageCollection 触发垃圾回收的阈值
     */
    public AbstractManageableCaptchaService(final CaptchaStore captchaStore,
                                            final CaptchaFactory captchaFactory,
                                            final int minGuarantedStorageDelayInSeconds,
                                            final int captchaStoreMaxSize,
                                            final int captchaStoreSizeBeforeGarbageCollection) {
        super(captchaStore, captchaFactory);
        this.minGuarantedStorageDelayInSeconds = minGuarantedStorageDelayInSeconds;
        this.captchaStoreMaxSize = captchaStoreMaxSize;
        this.captchaStoreSizeBeforeGarbageCollection = captchaStoreSizeBeforeGarbageCollection;
    }

    /**
     * @return 当前存储的对象数量
     */
    public long getCaptchaStoreSize() {
        return getCaptchaStore().size();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Captcha generateAndStore(final String id, final Locale locale) {
        if (isCaptchaStoreFull()) {
            final long collected = garbageCollectCaptchaStore();

            if (1 > collected) {
                //impossible ! has to wait
                throw new IllegalStateException("Store is full, try to increase CaptchaStore Size or" +
                        "to decrease time out, or to decrease CaptchaStoreSizeBeforeGrbageCollection");
            }
            return this.generateAndStore(id, locale);
        }

        if (isCaptchaStoreQuotaReached()) {
            garbageCollectCaptchaStore();
        }

        return super.generateAndStore(id, locale);
    }

    /**
     * 对存储设备进行垃圾回收.
     *
     * @return 返回被回收的验证码数量
     */
    public long garbageCollectCaptchaStore() {
        final long now = System.currentTimeMillis();
        final Collection<String> garbageCollectableCaptchaIds = getGarbageCollectableCaptchaIds(now);

        return this.garbageCollectCaptchaStore(now, garbageCollectableCaptchaIds.iterator());
    }

    /**
     * 获取可以垃圾回收的 id.
     *
     * @param now 垃圾回收执行时间
     * @return 可以回收的id
     */
    private Collection<String> getGarbageCollectableCaptchaIds(final long now) {
        //construct a new collection in order to avoid iterations synchronization pbs :
        // this may cause a captcha disparition if a new captcha is asked between
        // this call and the effective removing from the store!
        final Set<String> garbageCollectableCaptchas = new HashSet<String>();
        final CaptchaStore store = getCaptchaStore();

        //the time limit under which captchas are collectable
        final long garbageTime = now - 1000 * getMinGuarantedStorageDelayInSeconds();
        if (oldestCaptcha < garbageTime) {
            // iterate to find out if the captcha is perimed

            final Iterator<String> it = store.keys().iterator();
            while (it.hasNext()) {
                final String id = it.next();
                final Captcha captcha = store.get(id);

                if (null != captcha) {
                    final long effTime = captcha.hasChallengeBeenCalled() ? captcha.getEffectiveTime() : now;

                    oldestCaptcha = Math.min(effTime, oldestCaptcha);
                    if (effTime < garbageTime) {
                        garbageCollectableCaptchas.add(id);
                    }
                }
            }
        }
        return garbageCollectableCaptchas;
    }

    /**
     * Garbage collect the captcha store, means all old captcha (captcha in the store wich has been stored more than the
     * MinGuarantedStorageDelayInSecond
     */
    private long garbageCollectCaptchaStore(final long now, final Iterator<String> garbageCollectableCaptchaIds) {
        // this may cause a captcha disparition if a new captcha is asked between
        // this call and the effective removing from the store!
        final CaptchaStore store = getCaptchaStore();

        long collected = 0;
        while (garbageCollectableCaptchaIds.hasNext()) {
            final String id = garbageCollectableCaptchaIds.next();
            final Captcha captcha = store.get(id);

            // 调用过且超时 (有可能又新生成了)
            if (isGarbageCollectable(now, captcha)) {
                collected += 1;
                store.remove(id);
            }
        }
        return collected;
    }

    protected boolean isGarbageCollectable(final long now, final Captcha captcha) {
        final long garbageTime = now - 1000 * getMinGuarantedStorageDelayInSeconds();
        return null == captcha || (captcha.hasChallengeBeenCalled() && captcha.getEffectiveTime() < garbageTime);
    }

    /**
     * @return 是否存储设备已满
     */
    protected boolean isCaptchaStoreFull() {
        return 0 < getCaptchaStoreMaxSize() && getCaptchaStoreSize() >= getCaptchaStoreMaxSize();
    }

    /**
     * @return 是否存储设备已经达到垃圾回收的阀值
     */
    protected boolean isCaptchaStoreQuotaReached() {
        final int threshold = getCaptchaStoreSizeBeforeGarbageCollection();
        return 0 < threshold && getCaptchaStoreSize() >= threshold;
    }

    /* ***********************************
     *          GETTER / SETTER
     * ********************************* */

    public int getMinGuarantedStorageDelayInSeconds() {
        return minGuarantedStorageDelayInSeconds;
    }

    public void setMinGuarantedStorageDelayInSeconds(int minGuarantedStorageDelayInSeconds) {
        this.minGuarantedStorageDelayInSeconds = minGuarantedStorageDelayInSeconds;
    }

    public int getCaptchaStoreMaxSize() {
        return captchaStoreMaxSize;
    }

    public void setCaptchaStoreMaxSize(final int captchaStoreMaxSize) {
        this.captchaStoreMaxSize = captchaStoreMaxSize;
    }

    public int getCaptchaStoreSizeBeforeGarbageCollection() {
        return captchaStoreSizeBeforeGarbageCollection;
    }

    public void setCaptchaStoreSizeBeforeGarbageCollection(final int captchaStoreSizeBeforeGarbageCollection) {
        this.captchaStoreSizeBeforeGarbageCollection = captchaStoreSizeBeforeGarbageCollection;
    }
}
